import { Injectable, Logger, forwardRef, Inject, } from '@nestjs/common';
import { InjectModel } from '@nestjs/sequelize';
import { Model, ModelCtor } from 'sequelize-typescript';
import { Op, fn, col } from "sequelize";
import dayjs from 'dayjs';
import type { ManipulateType } from "dayjs";
import { RESPONSE_CODE } from "@/enums/code";
import { getUuidString, getUuid } from "@/utils/uuid";
import { DICT_TYPE } from "@/enums/dict";
import { getPageWhere, getPageData, } from '@/utils/page'
import { TASK_CRON_MAP } from '@/enums/task'
import { TasksService } from '@/sysModules/task/task.service';
import { BusService } from '@/sysModules/bus/bus.service';
import { DictService } from '@/modules/admin/dict/dict.service';
import { Indicator } from './indicator.model';
import { IndicatorDataSecond } from './indicatorDataSecond.model';
import { IndicatorDataMinute } from './indicatorDataMinute.model';
import { IndicatorDataHour } from './indicatorDataHour.model';
import { IndicatorDataDay } from './indicatorDataDay.model';
import { IndicatorDataMonth } from './indicatorDataMonth.model';
import { IndicatorDataYear } from './indicatorDataYear.model';
import {
  IndicatorDto,
  IndicatorDataType,
  DataCalcTypeMap,
  IndicatorDataMap,
  GetIndicatorValueDto,
  AddIndicatorDto,
  GetIndicatorPageTreeDto,
  GetIndicatorPageDto,
  DeleteIndicatorDto,
  GetIndicatorTagIndexListDto,
  CheckFormulaDto,
  DataCalcType,
  TaskType,
  TimeType
} from './indicator.dto'



@Injectable()
export class IndicatorService {
  constructor(
    @InjectModel(Indicator)
    private indicatorModel: typeof Indicator,

    @InjectModel(IndicatorDataSecond)
    private indicatorDataSecondModel: typeof IndicatorDataSecond,

    @InjectModel(IndicatorDataMinute)
    private indicatorDataMinuteModel: typeof IndicatorDataMinute,

    @InjectModel(IndicatorDataHour)
    private indicatorDataHourModel: typeof IndicatorDataHour,

    @InjectModel(IndicatorDataDay)
    private indicatorDataDayModel: typeof IndicatorDataDay,

    @InjectModel(IndicatorDataMonth)
    private indicatorDataMonthModel: typeof IndicatorDataMonth,

    @InjectModel(IndicatorDataYear)
    private indicatorDataYearModel: typeof IndicatorDataYear,

    @Inject(forwardRef(() => TasksService))
    private tasksService: TasksService,

    // @Inject(forwardRef(() => BusService))
    private busService: BusService,
    private dictService: DictService

  ) {

    this.modelMap = {
      [IndicatorDataType.Second]: {
        model: this.indicatorDataSecondModel,
        dataType: 'second',
        subtractCount: 0
      },
      [IndicatorDataType.Minute]: {
        model: this.indicatorDataMinuteModel,
        dataType: 'minute',
        subtractCount: 1
      },
      [IndicatorDataType.Hour]: {
        model: this.indicatorDataHourModel,
        dataType: 'hour',
        subtractCount: 1
      },
      [IndicatorDataType.Day]: {
        model: this.indicatorDataDayModel,
        dataType: 'day',
        subtractCount: 0
      },
      [IndicatorDataType.Month]: {
        model: this.indicatorDataMonthModel,
        dataType: 'month',
        subtractCount: 0
      },
      [IndicatorDataType.Year]: {
        model: this.indicatorDataYearModel,
        dataType: 'year',
        subtractCount: 0
      },
    }
  }

  private readonly logger = new Logger(IndicatorService.name);
  private modelMap: { [key: number]: { model: ModelCtor<Model<any, any>>, dataType: ManipulateType, subtractCount: number } };
  private readonly format = 'YYYY-MM-DD HH:mm:ss';
  private timeTypeMap = {
    "1": {
      name: "分钟",
      ename: "M",
    },
    "2": {
      name: "小时",
      ename: "H",
    },
    "3": {
      name: "天",
      ename: "D",
    },
    "4": {
      name: "月",
      ename: "L",
    },
    "5": {
      name: "年",
      ename: "Y",
    },
  }

  /**
   * 添加监测点
   * @method post
   * @param addIndicatorDto
   * @returns
   */
  async addIndicator(addIndicatorDto: AddIndicatorDto) {
    const { indicatorCode, nodeGroupRelations } = addIndicatorDto
    let indicatorChildrenList: any[] = [];
    const id = getUuidString();
    const indicator = await this.indicatorModel.findOne({
      where: { indicatorCode },
    });
    if (indicator) {
      return {
        code: RESPONSE_CODE.ERROR,
        data: [],
        msg: "测点编码已存在",
      }
    }
    if (nodeGroupRelations?.length) {
      indicatorChildrenList = nodeGroupRelations.map((item: any) => {
        return {
          ...addIndicatorDto,
          ...item,
          parentId: id,
          parentIndicatorCode: indicatorCode,
        };
      });
    }
    const indicatorParentList = [{ ...addIndicatorDto, id }];
    const AllList = [...indicatorParentList, ...indicatorChildrenList];
    // 批量添加
    await this.indicatorModel.bulkCreate(AllList, {
      validate: true,
    });
    return {
      code: RESPONSE_CODE.SUCCESS,
      data: [],
      msg: null,
    }
  }

  /**
   * 更新监测点
   * @method post
   * @param 
   * @returns
   */
  async updateIndicator() { }

  /**
   * 指标树分页
   * @method get
   * @param query query 
   * @returns
   */
  async getIndicatorPageTree(getIndicatorPageTreeDto: GetIndicatorPageTreeDto) {
    const {
      page,
      size,
      factoryId,
      indicatorCode,
      indicatorName,
      indicatorDesc,
      sourceType,
      dataType,
      tag,
    } = getIndicatorPageTreeDto;

    // 获取数据类型
    const dataTypeList = await this.dictService.getDictItem(
      DICT_TYPE.MONITORING_POINT_DATA_TYPE
    );

    // 获取数据来源
    const sourceTypeList = await this.dictService.getDictItem(
      DICT_TYPE.MONITORING_POINT_DATA_SOURCE
    );

    // 获取时间类型
    const timeTypeList = await this.dictService.getDictItem(
      DICT_TYPE.MONITORING_POINT_DATA_TIME_TYPE
    );
    const query = {};
    if (indicatorCode) {
      query["indicatorCode"] = { [Op.like]: `${indicatorCode}%` };
    }
    if (indicatorName) {
      query["indicatorName"] = { [Op.like]: `${indicatorName}%` };
    }
    if (indicatorDesc) {
      query["indicatorDesc"] = { [Op.like]: `${indicatorDesc}%` };
    }
    if (tag) {
      query["indicatorCode"] = { [Op.like]: `${tag}%` };
    }
    if (factoryId) {
      query["factoryId"] = factoryId;
    }
    if (sourceType) {
      query["sourceType"] = sourceType;
    }
    if (dataType) {
      query["dataType"] = dataType;
    }
    query["parentId"] = null;

    const { rows, count } = await this.indicatorModel.findAndCountAll({
      where: query,
      order: [["createTime", "DESC"]],
      ...getPageWhere(page, size)
    });

    this.logger.debug(rows, count, query)

    const ids = rows.map((item) => item.dataValues.id);

    const { rows: childrenRows } = await this.indicatorModel.findAndCountAll({
      where: { parentId: ids },
    });

    const childrenList = childrenRows
      .map((item) => {
        const {
          id,
          parentId,
          weight,
          indicatorName,
          dataType,
          indicatorCode,
          dataTimeType,
          parentIndicatorCode,
          sourceType,
          sortId,
          createTime,
          updateTime,
        } = item.dataValues;
        return {
          id,
          parentId,
          weight,
          name: `Node ${id}`,
          indicatorName,
          indicatorCode,
          tag: indicatorCode,
          parentIndicatorCode,
          sortId,
          dataType: dataTypeList.find((i) => i.value === String(dataType)).label,
          timeType: timeTypeList.find((i) => i.value === String(dataTimeType))
            .label,
          resourceType: sourceTypeList.find((i) => i.value === String(sourceType))
            .label,
          createTime: createTime
            ? dayjs(createTime).format("YYYY-MM-DD HH:mm:ss")
            : null,
          updateTime: updateTime
            ? dayjs(updateTime).format("YYYY-MM-DD HH:mm:ss")
            : null,
        };
      })
      .sort((a, b) => a.sortId - b.sortId);
    const list = rows.map((item) => {
      const {
        id,
        parentId,
        weight,
        indicatorName,
        dataType,
        indicatorCode,
        dataTimeType,
        parentIndicatorCode,
        sourceType,
        sortId,
        createTime,
        updateTime,
      } = item.dataValues;

      const children = childrenList.filter((c) => c.parentId === id);
      return {
        id,
        parentId,
        weight,
        name: `Node ${id}`,
        indicatorName,
        indicatorCode,
        tag: indicatorCode,
        parentIndicatorCode,
        sortId,
        dataType: dataTypeList.find((i) => i.value === String(dataType)).label,
        timeType: timeTypeList.find((i) => i.value === String(dataTimeType))
          .label,
        resourceType: sourceTypeList.find((i) => i.value === String(sourceType))
          .label,
        createTime: createTime
          ? dayjs(createTime).format("YYYY-MM-DD HH:mm:ss")
          : null,
        updateTime: updateTime
          ? dayjs(updateTime).format("YYYY-MM-DD HH:mm:ss")
          : null,
        children: children.length ? children : undefined,
      };
    });

    return {
      code: RESPONSE_CODE.SUCCESS,
      data: {
        list,
        ...getPageData(page, size, count, rows),
        records: undefined
      },
      msg: null,
    }
  }

  /**
   * 监测点分页
   * @method post
   * @param getIndicatorPageDto
   * @returns
   */
  async getIndicatorPage(getIndicatorPageDto: GetIndicatorPageDto) {
    const {
      page,
      size,
      dataType,
      indicatorOrName,
      sourceType,
      timeType,
      factoryId,
      appId,
      orgId,
      renterId,
    } = getIndicatorPageDto;

    let query = {};
    if (indicatorOrName) {
      query = {
        ...query,
        [Op.or]: [
          {
            indicatorCode: {
              [Op.like]: `%${indicatorOrName}%`,
            },
          },
          {
            indicatorName: {
              [Op.like]: `%${indicatorOrName}%`,
            },
          },
        ],
      };
    }
    if (sourceType) {
      query["sourceType"] = sourceType;
    }
    if (dataType) {
      query["dataType"] = dataType;
    }
    if (timeType) {
      query["timeType"] = timeType;
    }
    if (factoryId) {
      query["factoryId"] = factoryId;
    }

    const { count, rows } = await Indicator.findAndCountAll({
      where: query,
      order: [["createTime", "ASC"]],
      ...getPageWhere(page, size)
    });
    return {
      code: RESPONSE_CODE.SUCCESS,
      data: getPageData(page, size, count, rows),
      msg: null,
    }
  }

  /**
   * 监测点列表
   * @method get
   * @param id 
   */
  async getIndicatorList() { }

  /**
   * 获取指标信息
   * @method get
   * @param id 
   */
  async getIndicatorInfo(id: string) {
    const indicatorInfo = await this.indicatorModel.findOne({ where: { id } });
    let result = {};
    if (!indicatorInfo) {
      return {
        code: RESPONSE_CODE.SUCCESS,
        data: null,
        msg: null,
      }
    }
    this.logger.debug(indicatorInfo)
    if (indicatorInfo.dataValues.parentId === null) {
      const parentId = indicatorInfo.dataValues.id;
      const children = (await this.indicatorModel.findAll({ where: { parentId } })).sort(
        (a, b) => a.dataValues.sortId - b.dataValues.sortId
      );

      result = {
        ...indicatorInfo.dataValues,
        nodeRelationByGroupCodes: [
          {
            groupCode: getUuid(),
            nodeGroupRelations: children.map((item) => ({
              dataCalcType: item.dataValues.dataCalcType,
              dataTimeType: item.dataValues.dataTimeType,
              indicatorCode: item.dataValues.indicatorCode,
              indicatorName: item.dataValues.indicatorName,
              mergeType: item.dataValues.mergeType,
              sortId: item.dataValues.sortId,
              sourceType: item.dataValues.sourceType,
            })),
          },
        ],
      };
    }
    if (indicatorInfo.dataValues.parentId !== null) {
      const id = indicatorInfo.dataValues.parentId;
      const parent = await this.indicatorModel.findOne({ where: { id } });
      const children = (
        await this.indicatorModel.findAll({ where: { parentId: id } })
      ).sort((a, b) => a.dataValues.sortId - b.dataValues.sortId);

      result = {
        ...parent?.dataValues,
        nodeRelationByGroupCodes: [
          {
            groupCode: getUuid(),
            nodeGroupRelations: children.map((item) => ({
              dataCalcType: item.dataValues.dataCalcType,
              dataTimeType: item.dataValues.dataTimeType,
              indicatorCode: item.dataValues.indicatorCode,
              indicatorName: item.dataValues.indicatorName,
              mergeType: item.dataValues.mergeType,
              sortId: item.dataValues.sortId,
              sourceType: item.dataValues.sourceType,
            })),
          },
        ],
      };
    }
    return {
      code: RESPONSE_CODE.SUCCESS,
      data: result,
      msg: null,
    }
  }

  /**
   * 删除
   * @method post
   * @param deleteIndicatorDto 
   * @returns
   */
  async deleteIndicator(deleteIndicatorDto: DeleteIndicatorDto) {
    const { id, parentIndicatorCode } = deleteIndicatorDto;
    if (!parentIndicatorCode) {
      const indicator = await this.indicatorModel.findOne({
        where: {
          [Op.or]: [{ id }, { parentId: id }],
        }
      })
      indicator && await this.tasksService.deleteTask(indicator.dataValues.indicatorCode)
      indicator && indicator.destroy()
      return {
        code: RESPONSE_CODE.SUCCESS,
        data: null,
        msg: null,
      };
    }
    const indicator = await this.indicatorModel.findOne({
      where: { id }
    })
    indicator && await this.tasksService.deleteTask(indicator.dataValues.indicatorCode)
    indicator && indicator.destroy()

    return {
      code: RESPONSE_CODE.SUCCESS,
      data: null,
      msg: null,
    };
  }

  /**
  * 生成采集指标
  * @param checkFormulaDto 
  * @method post
  * @returns 
  */
  async getIndicatorTagIndexList(getIndicatorTagIndexListDto: GetIndicatorTagIndexListDto) {
    const { indicatorCode, dataTimeType, dataCalcType, sourceType } = getIndicatorTagIndexListDto
    // 获取时间类型
    const timeTypeList: { value: TimeType; sortOrder: number }[] =
      await this.dictService.getDictItem(DICT_TYPE.MONITORING_POINT_DATA_TIME_TYPE);
    const _timeTypeList = timeTypeList.filter(
      (item) => Number(item.value) > Number(dataTimeType)
    );
    const data = _timeTypeList
      .map((item) => ({
        indicatorCode: `${indicatorCode}_${this.timeTypeMap[item.value].ename
          }undefined`,
        indicatorName: `${indicatorCode}_${this.timeTypeMap[item.value].name}_`,
        dataTimeType: Number(item.value),
        dataCalcType: Number(dataCalcType),
        sortId: item.sortOrder,
      }))
      .sort((a, b) => a.sortId - b.sortId);
    return {
      code: RESPONSE_CODE.SUCCESS,
      data: data,
      msg: null,
    }
  }

  /**
   * 校验formula
   * @param checkFormulaDto 
   * @method post
   * @returns 
   */
  async checkFormula(checkFormulaDto: CheckFormulaDto) {
    const { formula, isNull, dataTimeType } = checkFormulaDto;

    const params: {
      indicatorCode: string;
      dataCalcType: DataCalcType;
      dataTimeType: TaskType;
      isNull: any;
      formula: string;
    } = {
      formula,
      isNull,
      dataTimeType,
      indicatorCode: '',
      dataCalcType: 0,
    };

    const result = await this.calculateIndicatorValue(params)

    return {
      code: result === false ? RESPONSE_CODE.ERROR : RESPONSE_CODE.SUCCESS,
      data: {
        result: result === false ? "" : result,
      },
      msg: result === false ? "校验不通过" : null,
    };
  }

  /**
   * 监测点公式常用公式示例
   * @param 
   * @returns
   */
  async expressionIndicator() {
    const mock = [
      {
        functionName: "AVG",
        functionNameScript: "计算并求出一段时间指定业务单位的指定标签的平均值",
        paramsDescripts: [
          {
            paramsName: "Flag",
            descript: "指标flag",
            example: "Bus.Avg[T1,2,minute,指标名]",
          },
        ],
      },
      {
        functionName: "MAX",
        functionNameScript: "计算并求出一段时间指定业务单位的指定标签的最大值",
        paramsDescripts: [
          {
            paramsName: "Flag",
            descript: "指标flag",
            example: "Bus.Max[T1,2,minute,指标名]",
          },
        ],
      },
      {
        functionName: "MIN",
        functionNameScript: "计算并求出一段时间指定业务单位的指定标签的最小值",
        paramsDescripts: [
          {
            paramsName: "Flag",
            descript: "指标flag",
            example: "Bus.Min[T1,2,minute,指标名]",
          },
        ],
      },
      {
        functionName: "FIR",
        functionNameScript: "计算并求出一段时间指定业务单位的指定标签的第一条值",
        paramsDescripts: [
          {
            paramsName: "Flag",
            descript: "指标flag",
            example: "Bus.First[T1,2,minute,指标名]",
          },
        ],
      },
      {
        functionName: "LAS",
        functionNameScript:
          "计算并求出一段时间指定业务单位的指定标签的最后一条值",
        paramsDescripts: [
          {
            paramsName: "Flag",
            descript: "指标flag",
            example: "Bus.Last[T1,2,minute,指标名]",
          },
        ],
      },
      {
        functionName: "COU",
        functionNameScript: "计算并求出一段时间指定业务单位的指定标签的个数",
        paramsDescripts: [
          {
            paramsName: "Flag",
            descript: "指标flag",
            example: "Bus.Count[T1,2,minute,指标名]",
          },
        ],
      },
      {
        functionName: "RANDOM",
        functionNameScript: "计算两个值之间的随机数",
        paramsDescripts: [
          {
            paramsName: "Flag",
            descript: null,
            example: "Bus.Random[0,100]",
          },
        ],
      },
      {
        functionName: "SUM",
        functionNameScript: "计算并求出一段时间指定业务单位的指定标签的累计值",
        paramsDescripts: [
          {
            paramsName: "Flag",
            descript: "指标flag",
            example: "Bus.Sum[T1,2,minute,指标名]",
          },
        ],
      },
    ];
    return {
      code: RESPONSE_CODE.SUCCESS,
      data: mock,
      msg: null,
    }
  }

  /**
  * 通用方法
  * @param methods 方法
  * @param query query 条件
  * @returns 
  */
  async publicMethods(methods: string, query: any) {
    return await this.indicatorModel[methods]({ ...query });
  }

  /**
   * 计算指标数据
   * @param data IndicatorDto
   * @returns 
   */
  private async calculateIndicatorValue(data: IndicatorDto): Promise<number | boolean | null> {
    const { dataTimeType, indicatorCode, parentId, formula, dataCalcType, parentIndicatorCode } = data
    let value = null;
    const endTime = new Date();
    const startTime = new Date(
      dayjs(endTime)
        .subtract(
          this.modelMap[dataTimeType].subtractCount,
          this.modelMap[dataTimeType].dataType
        )
        .startOf(this.modelMap[dataTimeType].dataType)
        .format(this.format)
    );

    if (indicatorCode) {
      const sameData = await this.modelMap[dataTimeType].model.findOne({
        where: {
          createTime: endTime,
          indicatorCode: indicatorCode,
        },
      });
      if (
        sameData ||
        dayjs(endTime).format(this.format) !==
        dayjs(endTime)
          .startOf(this.modelMap[dataTimeType].dataType)
          .format(this.format)
      )
        return false;
    }
    if (!parentId) {
      value = this.busService.calculate(formula);
      return value;
    }
    const indicator = await this.indicatorModel.findOne({
      where: { id: parentId },
    });
    if (!indicator) return value;
    const { dataTimeType: _dataTimeType, digit } = indicator.dataValues;
    const res = await this.modelMap[dataTimeType].model.findAll({
      attributes: [
        [
          fn(
            "ROUND",
            fn(DataCalcTypeMap[dataCalcType], col("value")),
            digit ?? 2
          ),
          "value",
        ],
      ],
      where: {
        parentIndicatorCode,
        createTime: {
          [Op.gt]: startTime,
          [Op.lte]: endTime,
        },
      },
    });

    value = res[0].dataValues.value;
    return value
  }

  /**
   * 数据指标写入数据库
   * @param data IndicatorDto
   */
  private async writeIndicatorData(data: IndicatorDto) {
    const { indicatorCode, dataTimeType, dataCalcType, mergeType, parentIndicatorCode } = data
    const value = await this.calculateIndicatorValue(data)
    if (value === false) return
    await this.modelMap[dataTimeType].model.create({
      parentIndicatorCode: parentIndicatorCode ?? indicatorCode,
      indicatorCode,
      dataCalcType,
      mergeType,
      dataTimeType,
      value,
    })
  }

  /**
   * 监测点指标数据任务
   * @param data IndicatorDto
   */
  private async indicatorDataTask(data: IndicatorDto) {
    const { indicatorCode, dataTimeType } = data
    this.tasksService.addTask(indicatorCode, TASK_CRON_MAP[dataTimeType], () => this.writeIndicatorData(data))
  }

  /**
   * 监测点任务
   */
  async indicatorTask() {
    const indicatorList = await this.publicMethods('findAll', {})
    const jobs = this.tasksService.getAllTask()
    const jobsNames:string[] = []
    jobs.forEach((_,name) => {
      jobsNames.push(name)
    })
    indicatorList.map(item => {
      const {indicatorCode } = item.dataValues
      if(!jobsNames.includes(indicatorCode)){
        this.indicatorDataTask(item.dataValues)
      }
    })
  }

  /**
   * 获取指标值
   * @param data IndicatorDto
   * @returns 
   */
  async getIndicatorValue(data: GetIndicatorValueDto) {
    const { indicatorCode, dataTimeType, duration, dataCalcType } = data;
    let value = 0;
    const indicator = await this.indicatorModel.findOne({
      where: { indicatorCode },
    });
    if (!indicator) return value.toFixed(2);

    const type = IndicatorDataMap[dataTimeType];

    const endTime = new Date();
    const startTime = new Date(
      dayjs(endTime)
        .subtract(duration, this.modelMap[type].dataType)
        .startOf(this.modelMap[type].dataType)
        .format(this.format)
    );

    const res = await this.modelMap[type].model.findAll({
      attributes: [
        [
          fn("ROUND", fn(DataCalcTypeMap[dataCalcType], col("value")), 2),
          "value",
        ],
      ],
      where: {
        parentIndicatorCode: indicator.dataValues.indicatorCode,
        createTime: {
          [Op.gte]: startTime,
          [Op.lt]: endTime,
        },
      },
    });
    value = res.length ? res[0].dataValues.value : 0;
    return value;
  }
}
